using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace Voice
{
    public delegate void BufferDoneEventHandler(IntPtr data, int size);


    public class WavInDevice
    {
        private int m_Index = 0;
        private string _mName="";
        public string MName
        {
            get { return _mName; }
            set { _mName = value; }
        }

        private int m_Channels = 1;

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="index">Device index in devices.</param>
        /// <param name="name">Device name.</param>
        /// <param name="channels">Number of audio channels.</param>
        internal WavInDevice(int index, string name, int channels)
        {
            m_Index = index;
            MName = name;
            m_Channels = channels;
        }


        #region Properties Implementation

        /// <summary>
        /// Gets device name.
        /// </summary>
        public string Name
        {
            get { return MName; }
        }

        /// <summary>
        /// Gets number of input channels(mono,stereo,...) supported.
        /// </summary>
        public int Channels
        {
            get { return m_Channels; }
        }


        /// <summary>
        /// Gets device index in devices.
        /// </summary>
        internal int Index
        {
            get { return m_Index; }
        }

        #endregion
    }

    
    internal class WaveInHelper
    {
        public static void Try(int err)
        {
            if (err != WINMM_DLL.MMSYSERR_NOERROR)
                throw new Exception(err.ToString());
        }
    }

    internal class WaveInBuffer : IDisposable
    {
        public WaveInBuffer NextBuffer;

        private AutoResetEvent m_RecordEvent = new AutoResetEvent(false);
        private IntPtr m_WaveIn;

        private WINMM_DLL.WaveHdr m_Header;
        private byte[] m_HeaderData;
        private GCHandle m_HeaderHandle;
        private GCHandle m_HeaderDataHandle;

        private bool m_Recording;

        internal static void WaveInProc(IntPtr hdrvr, int uMsg, int dwUser, ref WINMM_DLL.WaveHdr wavhdr, int dwParam2)
        {
            if (uMsg == WINMM_DLL.MM_WIM_DATA)
            {
                try
                {
                    GCHandle h = (GCHandle)wavhdr.dwUser;
                    WaveInBuffer buf = (WaveInBuffer)h.Target;
                    buf.OnCompleted();
                }
                catch
                {
                }
            }
        }

        public WaveInBuffer(IntPtr waveInHandle, int size)
        {
            m_WaveIn = waveInHandle;

            m_HeaderHandle = GCHandle.Alloc(m_Header, GCHandleType.Pinned);
            m_Header.dwUser = (IntPtr)GCHandle.Alloc(this);
            m_HeaderData = new byte[size];
            m_HeaderDataHandle = GCHandle.Alloc(m_HeaderData, GCHandleType.Pinned);
            m_Header.lpData = m_HeaderDataHandle.AddrOfPinnedObject();
            m_Header.dwBufferLength = size;
            WaveInHelper.Try(WINMM_DLL.waveInPrepareHeader(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header)));
        }
        ~WaveInBuffer()
        {
            Dispose();
        }

        public void Dispose()
        {
            if (m_Header.lpData != IntPtr.Zero)
            {
                WINMM_DLL.waveInUnprepareHeader(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header));
                m_HeaderHandle.Free();
                m_Header.lpData = IntPtr.Zero;
            }
            OnCompleted();
            //m_RecordEvent.Close();
            Thread.Sleep(1000);
            if (m_HeaderDataHandle.IsAllocated)
                m_HeaderDataHandle.Free();
            GC.SuppressFinalize(this);
        }

        public int Size
        {
            get { return m_Header.dwBufferLength; }
        }

        public IntPtr Data
        {
            get { return m_Header.lpData; }
        }

        public bool Record()
        {
            lock(this)
            {
                m_RecordEvent.Reset();
                m_Recording = WINMM_DLL.waveInAddBuffer(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header)) == WINMM_DLL.MMSYSERR_NOERROR;
                return m_Recording;
            }
        }

        public void WaitFor()
        {
            if (m_Recording)
                m_Recording = m_RecordEvent.WaitOne(500);
            else
                Thread.Sleep(1);
        }

        private void OnCompleted()
        {
            m_RecordEvent.Set();
            m_Recording = false;
        }
    }

    public class WaveInRecorder : IDisposable, INotifyPropertyChanged
    {
        private IntPtr m_WaveIn;
        private WaveInBuffer m_Buffers; // linked list
        private WaveInBuffer m_CurrentBuffer;
        private Thread m_Thread;
        private WaveFormat WaveInFormat;

        /// <summary>
        /// Gets all available input audio devices.
        /// </summary>
        public static WavInDevice[] Devices
        {
            get
            {
                List<WavInDevice> retVal = new List<WavInDevice>();
                // Get all available output devices and their info.                
                int devicesCount = WINMM_DLL.waveInGetNumDevs();
                for (int i = 0; i < devicesCount; i++)
                {
                    WAVEOUTCAPS pwoc = new WAVEOUTCAPS();
                    if (WINMM_DLL.waveInGetDevCaps((uint)i, ref pwoc, Marshal.SizeOf(pwoc)) == MMSYSERR.NOERROR)
                    {
                        retVal.Add(new WavInDevice(i, pwoc.szPname, pwoc.wChannels));
                    }
                }

                return retVal.ToArray();
            }
        }

        private int SaveBufSize;
        private int SaveBufCount;
        private WavInDevice _devRecordIn;
        public WavInDevice DevRecordIn
        {
            get { return _devRecordIn; }
            set
            {
                if (_devRecordIn == value) return;
                _devRecordIn = value;

                Dispose();
                m_Finished = false;
                if(_devRecordIn!=null)
                {
                    WaveInHelper.Try(WINMM_DLL.waveInOpen(out m_WaveIn, _devRecordIn.Index, WaveInFormat, m_BufferProc, 0, WINMM_DLL.CALLBACK_FUNCTION));
                    AllocateBuffers(SaveBufSize, SaveBufCount);
                    for (int i = 0; i < SaveBufCount; i++)
                    {
                        SelectNextBuffer();
                        m_CurrentBuffer.Record();
                    }
                    WaveInHelper.Try(WINMM_DLL.waveInStart(m_WaveIn));
                    m_Thread = new Thread(new ThreadStart(ThreadProc));
                    m_Thread.Start();
                    PropertyChanged(this , new PropertyChangedEventArgs("FilePath"));
                }

            }
        }
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
        public event EventHandler<VoiceCapturedEventArgs> WaveRecorded;

        private void OnWaveRecorded(IntPtr ptrData, int iSize, WaveFormat format)
        {
            VoiceCapturedEventArgs e = new VoiceCapturedEventArgs();// {Date = DateTime.Now, Format = format};
            e.Source = new byte[iSize];
            Marshal.Copy(ptrData, e.Source, 0, iSize);
            if (WaveRecorded!=null) //Igor 08.31
            WaveRecorded(this,e);
        }

        private bool m_Finished;

        private WINMM_DLL.WaveDelegate m_BufferProc = new WINMM_DLL.WaveDelegate(WaveInBuffer.WaveInProc);

        public static int DeviceCount
        {
            get { return WINMM_DLL.waveInGetNumDevs(); }
        }

        public WaveInRecorder(WavInDevice device, WaveFormat format, int bufferSize, int bufferCount)
        {
            SaveBufSize = bufferSize;
            SaveBufCount = bufferCount;
            WaveInHelper.Try(WINMM_DLL.waveInOpen(out m_WaveIn, device.Index, format, m_BufferProc, 0, WINMM_DLL.CALLBACK_FUNCTION));
            WaveInFormat = format;
            AllocateBuffers(bufferSize, bufferCount);
            for (int i = 0; i < bufferCount; i++)
            {
                SelectNextBuffer();
                m_CurrentBuffer.Record();
            }
            WaveInHelper.Try(WINMM_DLL.waveInStart(m_WaveIn));
            m_Thread = new Thread(new ThreadStart(ThreadProc));
            m_Thread.Start();
        }

        public WaveInRecorder(int device, WaveFormat format, int bufferSize, int bufferCount)
        {
          SaveBufSize = bufferSize;
            SaveBufCount = bufferCount;
            WaveInFormat = format;

            //* 
           WaveInHelper.Try(WINMM_DLL.waveInOpen(out m_WaveIn, device, format, m_BufferProc, 0, WINMM_DLL.CALLBACK_FUNCTION));
           AllocateBuffers(bufferSize, bufferCount);
           for (int i = 0; i < bufferCount; i++)
           {
               SelectNextBuffer();
               m_CurrentBuffer.Record();
           }
           WaveInHelper.Try(WINMM_DLL.waveInStart(m_WaveIn));
           m_Thread = new Thread(new ThreadStart(ThreadProc));
           m_Thread.Start();//*/
        }
        ~WaveInRecorder()
        {
            Dispose();
        }
        public void Dispose()
        {
            if (m_Thread != null)
                try
                {
                    m_Finished = true;
                    if (m_WaveIn != IntPtr.Zero)
                        WINMM_DLL.waveInReset(m_WaveIn);
                    WaitForAllBuffers();
                    m_Thread.Abort();
                    FreeBuffers();
                    if (m_WaveIn != IntPtr.Zero)
                        WINMM_DLL.waveInClose(m_WaveIn);
                }
                finally
                {
                    m_Thread = null;
                    m_WaveIn = IntPtr.Zero;
                }
            GC.SuppressFinalize(this);
        }
        private void ThreadProc()
        {
            //WaveInHelper.Try(WINMM_DLL.waveInStart(m_WaveIn));
            while (!m_Finished)
            {
                Advance();
                if (!m_Finished) OnWaveRecorded(m_CurrentBuffer.Data, m_CurrentBuffer.Size, WaveInFormat);
                m_CurrentBuffer.Record();
            }
        }
        private void AllocateBuffers(int bufferSize, int bufferCount)
        {
            FreeBuffers();
            if (bufferCount > 0)
            {
                m_Buffers = new WaveInBuffer(m_WaveIn, bufferSize);
                WaveInBuffer Prev = m_Buffers;
                try
                {
                    for (int i = 1; i < bufferCount; i++)
                    {
                        WaveInBuffer Buf = new WaveInBuffer(m_WaveIn, bufferSize);
                        Prev.NextBuffer = Buf;
                        Prev = Buf;
                    }
                }
                finally
                {
                    Prev.NextBuffer = m_Buffers;
                }
            }
        }
        private void FreeBuffers()
        {
            if (m_CurrentBuffer != null)
            {
                m_CurrentBuffer.Dispose();
                m_CurrentBuffer = null;
            }
            if (m_Buffers != null)
            {
                WaveInBuffer First = m_Buffers;
                m_Buffers = null;

                WaveInBuffer Current = First;
                do
                {
                    WaveInBuffer Next = Current.NextBuffer;
                    Current.Dispose();
                    Current = Next;
                } while(Current != First);
            }
        }
        private void Advance()
        {
            SelectNextBuffer();
            m_CurrentBuffer.WaitFor();
        }
        private void SelectNextBuffer()
        {
            m_CurrentBuffer = m_CurrentBuffer == null ? m_Buffers : m_CurrentBuffer.NextBuffer;
        }
        private void WaitForAllBuffers()
        {
            WaveInBuffer Buf = m_Buffers;
            while (Buf.NextBuffer != m_Buffers)
            {
                Buf.WaitFor();
                Buf = Buf.NextBuffer;
            }
        }
    }
}
